【筆記】 初學Node.js + Express.js 建立簡易餐廳清單


Posted by Double C on 2021-03-12

前言

首先,我也不知道會用這份筆記當作我的第一篇筆記,從2020年疫情以來就有想寫的念頭,但都被自己的惰性或身邊發生的事物而拖延,直到2021年,一個對我很不一樣的一年,身上多了不少責任,為了更加鞭策自己,嘗試看看寫筆記,並給自己訂了2021的目標,希望今年可以產出40篇以上的文章心得,那就以這份筆記當作一個好的開始吧。

什麼是Node.js 及 Express.js?

擷取自WIKI

  • Node.js 是能夠在伺服器端運行 JavaScript 的開放原始碼、跨平台 JavaScript 執行環境
    重點: Node.js是一個執行環境

  • Express.js 簡稱Express,是針對Node.js的web應用框架,在MIT許可證下作為自由及開放原始碼軟體發行。
    重點: 簡稱Express.js是一個web應用框架

簡單來說,原本的JavaScript是以前端程式設計所使用,而Node.js是以JavaScript為基礎所製成的伺服器端的執行環境。Express則是在Node.js的環境上,作為一個框架,方便讓人快速打造專案網站。

有興趣其內容的人可以再參考wiki。

資料來源: Node.js_wikiExpress.js_wiki

安裝準備

首先我們會用到的工具有很多,包含Node.js, VScode, NPM, Express, Handlebars(樣本系統)。

Node.js, VScode, NPM 可以直接從官網Download安裝,接下來會以開發的專案的流程開始進行。

專案開始 - Restaurant List

這是一個利用 Express.js、Bootstrap、Handlebars 所製作的網站,在這份專案中會了解到如何設定路由、製作 Handlebars 樣板、發送及接受 Handlebars 參數顯示動態資料、Handlebars 的迴圈應用等技巧。

restaurantList

Installation

開好VScode,並且輸入Ctrl + Shift + ``,打開Terminal,輸入以下資訊

#移動並創建本地資料夾
mkdir -p /installation/path && cd /installation/path

#從package.json中安裝express、express-handlebars套件
npm install express express-handlebars nodemon

Download Bootstrap, Popper.js, jQuery的js及CSS檔,或者利用CDN載入也可以

Features

  • 利用 Bootstrap 製作 RWD 網站樣式
  • 使用 Express.js、Handlebars 製作網站及路由設定
  • 把 JSON 資料帶入 Handlebars 樣板中動態呈現
  • 用 Query String 打造搜尋功能
  • 將網頁依照 Layouts 拆成多個部分樣版的 hadlebars 方便維護

Tools

  • Express - 應用程式框架
  • Handlebars - web 模板系統
  • Bootstrap - 開源前端框架

檔案架構

Folder Structer

流程

內容元件化

將內容分為header, footer, searchBar元件,show, index為主頁、結果分頁
將元件丟入partials資料夾,show及index放入views,將最重要的main放入layouts資料夾組織成一個網站。

載入Bootstrap, Popper.js, jQuery的js及CSS檔

將Bootstrap Popper.js jQuery的js、CSS檔放入public/javascriptspublic/samplesheets中。

設定路由(app.js)

重頭戲來啦!先載入Express跟Handlebars、設定Server Port、template engine及靜態檔案

  • Handlebars預設載入main.handlebars
  • 利用static讓Express不針對public資料夾進行處理。
//載入express
const express = require('express')
const app = express()

//設定port
const port = 3000

//載入handlebars
const exphbs = require('express-handlebars')

// setting template engine
app.engine('handlebars', exphbs({ defaultLayout: 'main' }))
app.set('view engine', 'handlebars')

// setting static files
app.use(express.static('public'))

使用get method設定網頁路由

  • 第5行 利用準備渲染網頁的資料(res)進行index網頁渲染,並將restaurantList.results以restaurants變數傳到index.handlebars。
  • 第8行 /restaurants/:restaurant_id中的:restaurant_id為從網頁request回來的參數,可進行後續資料處理,最後render到show.handlebars。
  • 第22行 render到index.handlebars中同時有兩個參數,一個是restaurants;另一個是keyword。
const restaurantList = require('./restaurant.json')
// routes setting
app.get('/', (req, res) => {
  // paste the restaurant data into 'index' partial template
  res.render('index', { restaurants: restaurantList.results })
})

app.get('/restaurants/:restaurant_id', (req, res) => {
  // find the restaurant_id in restaurantList and render 'show' partial template
  const restaurant = restaurantList.results.find((restaurant) => restaurant.id.toString() === req.params.restaurant_id)
  res.render('show', { restaurant: restaurant })
})

app.get('/search', (req, res) => {
  // console.log(req.query);
  // get user query string and filter restaurantList data
  const keyword = req.query.keyword
  const restaurants = restaurantList.results.filter((restaurant) => {
    return restaurant.name.toLowerCase().includes(keyword.toLowerCase())
  })

  res.render('index', { restaurants: restaurants, keyword: keyword })
})

設定main.handlebars

我們可以看到在<body>中有出現{{> header}}, {{{ body }}}{{}} 是handlebars解析純文字使用,而{{{}}} 是handlebars解析純html使用,而{{> header}} 是handlebars解析/public/partials/header.handlebars時,需透過>來指定檔案路徑解析header.handlebars。

  • 記得網頁還是需要經過reset.css的處理。
  • 網頁使用icons需載入fontawesome。
  • 網頁使用RWD需載入Bootstrap。

載入所需的css在<head>

<!-- ./views/layouts/main.handlebars -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Restaurant List</title>
  <link rel="stylesheet" href="/stylesheets/reset.css">
  <link rel="shortcut icon" href="https://assets-lighthouse.s3.amazonaws.com/uploads/image/file/6227/restaurant-list-logo.png" type="image/x-icon">
  <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous"/>
  <link rel="stylesheet" href="/stylesheets/bootstrap.css">
  <link rel="stylesheet" href="/stylesheets/all.css">
</head>

載入所需的js在<body>

<body>
  <!-- partial templates will replace the part of "body" here -->
  {{> header}}
  {{{ body }}}
  {{> footer}}
  <script src="/javascripts/jQuery.js"></script>
  <script src="/javascripts/popper.js"></script>
  <script src="/javascripts/bootstrap.js"></script>
</body>
</html>

設定header.handlebars footer.handlebars

將Header, Footer的html分別加入其handlebars。

設定index.handlebars

{{> searchBar}}如同main一樣,解析/public/partials/searchBar.handlebars時,透過>來指定檔案路徑解析searchBar.handlebars。

  • 第10、36行是利用{{#each}} 完成迴圈動態資料
  • {{#each}}內使用this作為該物件。
<!-- search bar -->
{{> searchBar}}
<!-- restaurant list -->
<div class="container mt-5">
  <div class="row">
    <div class="col-md-10 col-12 mx-auto">
      {{!-- change sorting list columns to rows--}}
      <div class="row row-cols-1 row-cols-md-3">
        {{!-- foreach loop for dynamic data--}}
        {{#each restaurants}}
        <div class="text-secondary mb-2 px-1 restaurant">
          <div class="card mb-3">
            <div class="cardImage">
              <img class="card-img-top" src="{{this.image}}" alt="{{this.name}}">
              {{!-- Add favorite switch btn --}}
              <btn class="favorite" type="button">
                <i class="far fa-heart fa-lg regular"></i>
                <i class="fas fa-heart fa-lg solid"></i>
              </btn>
            </div>

            <a href="/restaurants/{{this.id}}" class="card-body p-3">
              <h6 class="card-title mb-1">{{this.name}}</h6>

              <div class="restaurant-category mb-1">
                <i class="fas fa-utensils pr-2"></i> {{this.category}}
              </div>

              <span class="badge badge-pill badge-danger font-weight-normal">
                {{this.rating}}
                <i class="fas fa-star fa-xs"></i>
              </span>
            </a>
          </div>
        </div>
        {{/each}}
      </div>
    </div>
  </div>
</div>

設定show.handlebars

  • {{restaurant.name}}中的restaurant是透過index.handlebars抓取restaurant的物件。
<h1 class="mb-1 restaurant-show-title">{{restaurant.name}}</h1>
<div class="container">
  <div class="row">
    <div class="col-12 col-md-10 mx-auto">

      <p class="mb-1">
        <span class="text-secondary">
          <i class="fas fa-utensils pr-2"></i>
          類別:
        </span>
        {{restaurant.category}}
      </p>

      <p class="mb-1">
        <span class="text-secondary">
          <i class="fas fa-map-marker-alt pr-2"></i>
          地址:
        </span>
        {{restaurant.location}}
        <a href="{{restaurant.google_map}}" class="text-secondary" target="_blank">
          <i class="fas fa-location-arrow pr-2 fa-xs"></i>
        </a>
      </p>

      <p class="mb-1">
        <span class="text-secondary">
          <i class="fas fa-mobile-alt pr-2"></i>
          電話:
        </span>
        {{restaurant.phone}}
      </p>

      <p class="mb-5">
        {{restaurant.description}}
      </p>

      <img class="rounded mx-auto d-block mb-4 w-100" src="{{restaurant.image}}" alt="{{restaurant.name}}" style="max-width: 600px;">
    </div>
  </div>
</div>

監聽Web(app.js)

最後使用listen方法來監聽Web。

// Listen the server when it started
app.listen(port, () => {
  console.log(`Express is listening on localhost:${port}`)
})

測試專案

打開Terminal後,確認當前路徑為專案資料夾下後輸入nodemon app.js,會出現監聽的模式,這時候可以打開瀏覽器輸入 http://localhost:3000/ 測試結果囉!

#測試專案
nodemon app.js

總結

第一次使用Express及Handlebars發現兩樣的寫法都還蠻直覺的,但是Handlebars需要一點時間去適應它的寫法。餐廳清單只是初版的進行,最後還會依序增加功能,如:會員登入、我的最愛、留言等,會在其他篇中繼續更新,並網站重構,使其功能更加完整。

最後附上GitHub連結

Contribute

感謝Alpha Camp提供此次專案素材及資源


#node.js #Express.js #Handlebars







Related Posts

XSS lab (1)

XSS lab (1)

Day 115

Day 115

Day 121

Day 121


Comments